package common.gateway.interphaze;

import java.io.IOException;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;

import core.InMemoryTorrentDAO;
import core.domain.IOUtil;
import core.domain.PeerDesciptor;
import core.domain.RegistrationStatus;
import core.domain.Torrent;
import core.domain.messages.AbstractMessage;
import core.domain.messages.DisconnectionMessage;
import core.domain.messages.GetPeersForTorrentMessage;
import core.domain.messages.GetFilesMessage;
import core.domain.messages.AbstractMessage.MessageType;
import core.domain.messages.PingPongMessage;
import core.domain.messages.RegisterMessage;

public class TrackerGateway implements IGateWay {

	private ServerSocket serverSocket = null;
	static private int portNumber = 13468; // can be made to grab from property
											// file!
	private InMemoryTorrentDAO torrentDAO;
	private static TrackerGateway instance = null;

	public static TrackerGateway getInstance() {
		if (instance == null) {
			instance = new TrackerGateway();
		}
		return instance;
	}

	private TrackerGateway() {
		try {
			serverSocket = new ServerSocket(portNumber);
			torrentDAO = InMemoryTorrentDAO.getInstance();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("port unAvailable: " + 13468, e.getCause());
		}
	}

	public void acceptConnections() {
		pingerThread();
		while (true) {
			// TODO: STOP condition, from somekindof UI, or State manager
			handleRequests(serverSocket);
		}

	}

	private void pingerThread() {
		new Thread(new Runnable() {

			private long pingSleepInterval = 30l * 1000l;

			@Override
			public void run() {
				// TODO: stop condition...!!!
				while (true) {
					for (PeerDesciptor peer : torrentDAO.getAllPeers()) {
						String pingRequest = IOUtil.getInstance(PingPongMessage.class).objectToString(new PingPongMessage());
						try{
							Socket peerSocket = IOUtil.peerDescriptorToSocket(peer);
							IOUtil.writeMessageToSocket(peerSocket, pingRequest, false);
							PingPongMessage pongReply = IOUtil.getInstance(PingPongMessage.class).stringToObject(IOUtil.readMessageFromSocket(peerSocket, true));
							System.out.println("Pong received from " +peer +": "+pongReply);
						} catch (Exception e) {
							//when they r not available they throw exception
							torrentDAO.removePeer(peer);
							System.out.println(peer + " is removed from Tracker, ping failed!");
						}
					}
					try {
						Thread.sleep(pingSleepInterval);
					} catch (InterruptedException e) {
						e.printStackTrace();
						throw new RuntimeException("problem! pinging Thread interuption...", e.getCause());
					}
				}
			}
		}).start();
	}

	/**
	 * TODO: should factor this out to another file.
	 * 
	 * @author endre
	 * 
	 */
	private class TrackerIncomingRequestsHandler implements Runnable {
		public TrackerIncomingRequestsHandler(Socket socket) {
			System.out.println("connection accepted");
			this.clientSocket = socket;
		}

		Socket clientSocket;

		@Override
		public void run() {
			String messageFromSocket = IOUtil.readMessageFromSocket(clientSocket, false);
			System.out.println("message @ tracker: " + messageFromSocket);
			AbstractMessage unmarshalledObject = IOUtil.getInstance(MessageType.matchType(messageFromSocket)).stringToObject(messageFromSocket);
			// TODO: Beautify code with exctract to methods, or Helper
			// classes!
			if (unmarshalledObject instanceof GetPeersForTorrentMessage) {
				GetPeersForTorrentMessage peersForTorrentMessage = (GetPeersForTorrentMessage) unmarshalledObject;
				peersForTorrentMessage.setPeers(torrentDAO.getPeersForFile(peersForTorrentMessage.getTorrent()));
				String responseMessage = IOUtil.getInstance(GetPeersForTorrentMessage.class).objectToString(peersForTorrentMessage);
				IOUtil.writeMessageToSocket(clientSocket, responseMessage, true);
				// clientSocket.close(); may need to close socket here as
				// well or maybe in a finally place
			} else if (unmarshalledObject instanceof GetFilesMessage) {
				System.out.println("GetFilesMessage arrived @ tracker.");
				GetFilesMessage filesMessage = (GetFilesMessage) unmarshalledObject;
				filesMessage.setTorrentFiles(torrentDAO.getFiles());
				String responseMessage = IOUtil.getInstance(GetFilesMessage.class).objectToString(filesMessage);
				System.out.println("messageReturned: " + responseMessage);
				IOUtil.writeMessageToSocket(clientSocket, responseMessage, true);
				// clientSocket.close(); may need to close socket here as
				// well or maybe in a finally place
			} else if (unmarshalledObject instanceof RegisterMessage) {
				System.out.println("RegisterMessage arrived @ tracker.");
				RegisterMessage registerMessage = (RegisterMessage) unmarshalledObject;
				System.out.println("registerMessage arrived"+registerMessage);
				torrentDAO.reg(registerMessage.getTorrent(), registerMessage.getPeer());
				try {
					clientSocket.close();
				} catch (IOException e) {
					e.printStackTrace();
					throw new RuntimeException("could not close on register!", e.getCause());
				}
			} else if (unmarshalledObject instanceof DisconnectionMessage) {
				DisconnectionMessage disconnectionMessage = (DisconnectionMessage) unmarshalledObject;
				try {
					PeerDesciptor peer = IOUtil.socketToPeerDescriptor(clientSocket);
					clientSocket.close();
					Torrent torrent = disconnectionMessage.getTorrent();
					//unregistration of the peer's torrent or the whole peer itself!
					if (torrent != null) {
						torrentDAO.removePeerForTorrent(torrent, peer);
					} else {
						torrentDAO.removePeer(peer);
					}
				} catch (IOException e) {
					e.printStackTrace();
					throw new RuntimeException("problem at disconnection!", e.getCause());
				}
			} 
		}
	}

	public void handleRequests(ServerSocket serverSocket) {
		try {
			new Thread(new TrackerIncomingRequestsHandler(serverSocket.accept())).start();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("problem at accepting client connection!", e.getCause());
		}

	}

	@Override
	public void closeDown() {
		try {
			serverSocket.close();
			torrentDAO.reset();
			// torrentDAO = null; //donno check if needed
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	public static void main(String[] args) {
		TrackerGateway.getInstance().acceptConnections();
	}

}
